/* bbtweet
 * 
 */


package net.masker.bbtweet;

import net.rim.device.api.i18n.ResourceBundle;
import net.rim.device.api.notification.NotificationsConstants;
import net.rim.device.api.notification.NotificationsManager;
import net.rim.device.api.system.ApplicationDescriptor;
import net.rim.device.api.system.ApplicationManager;
import net.rim.device.api.system.Bitmap;
import net.rim.device.api.ui.Color;
import net.rim.device.api.ui.Field;
import net.rim.device.api.ui.FieldChangeListener;
import net.rim.device.api.ui.MenuItem;
import net.rim.device.api.ui.UiApplication;
import net.rim.device.api.ui.component.AutoTextEditField;
import net.rim.device.api.ui.component.BasicEditField;
import net.rim.device.api.ui.component.Dialog;
import net.rim.device.api.ui.component.EditField;
import net.rim.device.api.ui.component.LabelField;
import net.rim.device.api.ui.component.Menu;
import net.rim.device.api.ui.component.NumericChoiceField;
import net.rim.device.api.ui.component.ObjectChoiceField;
import net.rim.device.api.ui.component.PasswordEditField;
import net.rim.device.api.ui.component.RichTextField;
import net.rim.device.api.ui.component.SeparatorField;
import net.rim.device.api.ui.component.Status;
import net.rim.device.api.ui.container.MainScreen;
import net.rim.blackberry.api.homescreen.HomeScreen;
import net.rim.blackberry.api.mail.Folder;
import net.rim.blackberry.api.mail.Session;
import net.rim.blackberry.api.mail.Store;

public class bbtweet extends UiApplication implements bbtweetResource, FieldChangeListener
{
	private static Properties properties;
	private static ResourceBundle _resources = ResourceBundle.getBundle(BUNDLE_ID, BUNDLE_NAME);

	// Notifications ID net.masker.bbtweet.bbtweet.NOTIFICATIONS_ID_1
	public static final long NOTIFICATIONS_ID_1 = 0xc4340ba571f1f6c9L;
	
	// Dialog screens
	// Dialog updatingDlg = new Dialog("Updating Status",null,null,0,Bitmap.getBitmapResource("img/bbTweet_jde.png"));
	private static Dialog errDlg = new Dialog("Update not sent!",null,null,0,Bitmap.getPredefinedBitmap(Bitmap.EXCLAMATION));
	private Dialog updateDlg = new Dialog("Sending Update...",null,null,0,Bitmap.getBitmapResource("img/bbTweet_jde.png"))
	{
		public void close()
		{
			// if user tries to close dialog prompt to cancel update
			if (Dialog.ask(Dialog.D_YES_NO, "Cancel status update?", Dialog.NO) == Dialog.YES)
			{
				// Set message send status to failed
				properties = Properties.fetch();
                properties.setPushStatusUpdate(4);
                properties.save();
                _statusUpdater.stopSend();
			}
		}
	};
	static //Dialog successDlg = new Dialog("Success!",null,null,0,Bitmap.getBitmapResource("img/bbTweet1_jde.png"));
    
	// Threads
	ConnectionManager _connectionManager = new ConnectionManager();
	StatusUpdater _statusUpdater;
	StatusMonitor _statusMonitor;
	
	// Global Members of the Update Screen
	private UpdateScreen _updateScreen;
	private AutoTextEditField _statusUpdateField;
	private StatusField _charsField;
	private int charsRemaining;

/* Not using this for now
	private void notifyUser(String data)
	{
	  UiEngine ui = Ui.getUiEngine();
	  Screen screen = new Dialog(Dialog.D_OK, data, Dialog.OK,
	    Bitmap.getPredefinedBitmap(Bitmap.EXCLAMATION),
	    Manager.VERTICAL_SCROLL);
	  ui.pushGlobalScreen(screen, 1, ui.GLOBAL_QUEUE);
	} */
	
	public void fieldChanged(Field field, int context)
	{
		if (field == _statusUpdateField)
		{
			charsRemaining = 140 - _statusUpdateField.getTextLength();
			if (charsRemaining < 0)
				_charsField.setColor(Color.RED);
			else
				_charsField.setColor(Color.BLACK);
			_charsField.setText("Characters Remaining: "
					+ Integer.toString(charsRemaining));
		}
	}
	
	/**
	 * Launches an instance of bbtweet
	 * @param int instance to start
	 * @param 0 Set rollover icon and exit
	 * @param 1 Start Connection Manager thread
	 * @param 2 Launch GUI
	 */
	public bbtweet(int startup)
	{
		switch (startup)
		{
		case 0:
        	// Set Rollover icon for versions < 4.7 has problems
			// Catch exception for certain version of JDE
			try
			{	
				final Bitmap icon = Bitmap.getBitmapResource("img/bbTweet_jde.png");
        		final Bitmap rollIcon = Bitmap.getBitmapResource("img/bbTweet1_jde.png");
        
	        	invokeLater(new Runnable()
	        	{
	        		public void run()
	        		{
		        		ApplicationManager myApp = ApplicationManager.getApplicationManager();
		        		boolean wait = true;
		        		while (wait)
		        		{
		        			// Check if startup complete
		        			if (myApp.inStartup())
		        			{
		        				// sleep 1 sec, wait for startup to complete
		        				try
		        				{
		        					Thread.sleep(1000);
		        				}
		        				catch (Exception e)
		        				{
		        					// Couldn't sleep
		        				}
		        			}
		        			else
		        			{
		        				// Set rollover icon, capturing exception if index != 0
		        				try
		        				{
		        				HomeScreen.updateIcon(icon, 0);
		        				HomeScreen.setRolloverIcon(rollIcon, 0);
		        				}
		        				catch (IllegalArgumentException e)
		        				{
		        					// do nothing, index != 0
		        				}
		        				wait = false;
		        			}
		        		}
	        		}
	        	});
			}
			catch (Exception e)
			{
				// Do nothing if exception raised, JDE not supported
			}
			System.exit(0);
			break;

		case 1: // GUI
			// Fetch a working copy of properties
			properties = Properties.fetch();
			
			// Push the main screen instance onto the UI stack for rendering.						
			_charsField = new StatusField(StatusField.NON_FOCUSABLE);
			
			_statusUpdateField = new AutoTextEditField();
			_statusUpdateField.setChangeListener(this);
			_statusUpdateField.setText(properties.getStatusUpdate());			
			_statusUpdateField.setCursorPosition(_statusUpdateField.getTextLength());
			
			fieldChanged(_statusUpdateField, 99);
			
			_updateScreen = new UpdateScreen();
			_updateScreen.setTitle(new LabelField("What are you doing?" , LabelField.ELLIPSIS | LabelField.USE_ALL_WIDTH));
			_updateScreen.add(_statusUpdateField);
			_updateScreen.setStatus(_charsField);
			
			pushScreen(_updateScreen);
			
			// Start Threads
			_connectionManager.start();
			
			// If Options are not initialized, push Options screen onto the UI stack
			if (properties.getAuthenticationHash() == null)
				pushScreen(new OptionsScreen());
			else
				requestBackground();
			break;
		}
	}

	public static void main(String[] args)
    {
        if( args != null && args.length > 0 && args[0].equals("startup"))
        {            
            // Startup 
        	// Clear stale state info
        	properties = Properties.fetch();
        	properties.setConnectionManagerStarted(false);
        	properties.save();
        	
        	// Register Notification Source
        	NotificationsManager.registerSource( NOTIFICATIONS_ID_1, new Object()
            {
                public String toString()
                {
                    return _resources.getString(APPLICATION_TITLE);
                }
            }, NotificationsConstants.DEFAULT_LEVEL);
        	
        	// Set rollover icon
        	bbtweet theApp = new bbtweet(0);
        	theApp.setAcceptEvents(false);
        }
        else
        {       
            //Enter GUI
        	//Create new instance of app & start app on event thread
        	bbtweet theApp = new bbtweet(1);
        	theApp.enterEventDispatcher();
        }
    }
    
    final class AboutScreen extends MainScreen
    {
    	public AboutScreen()
    	{
    		// Pull version
    		ApplicationDescriptor descriptor = ApplicationDescriptor.currentApplicationDescriptor();
    		
    		// Refresh properties
    		properties = Properties.fetch();
    		
    		// Have the icon when clicked show the Application status
            LabelField title = new LabelField("About bbtweet" , LabelField.ELLIPSIS | LabelField.USE_ALL_WIDTH);
            setTitle(title);
            
            add(new RichTextField(_resources.getString(APPLICATION_TITLE), Field.NON_FOCUSABLE));
            add(new RichTextField(_resources.getString(APPLICATION_DESCRIPTION), Field.NON_FOCUSABLE));
            add(new RichTextField("Version: " + descriptor.getVersion()));
            add(new SeparatorField());
            add(new RichTextField("State: " + (properties.isBbtweetEnabled() ? "Enabled" : "Disabled"),Field.NON_FOCUSABLE));
            add(new RichTextField("Threads: " + String.valueOf(Thread.activeCount()),Field.NON_FOCUSABLE));
            add(new RichTextField("H: " + properties.getAuthenticationHash(),Field.NON_FOCUSABLE));
            add(new RichTextField("Update Retrieval: " + properties.getBbtweetStatusMessage()));
            
            // Trying to understand what inboxen are on the device
            String flist = "";
            Store store = Session.getDefaultInstance().getStore();
			Folder[] folders = store.list(Folder.INBOX);
			for (int i = 0; i < folders.length; ++i)
			{
				flist += "^ " + folders[i].getFullName() + ", " + Long.toString(folders[i].getId()) + " ";
			}
			add(new RichTextField("Folders: " + flist));
    	}
    	
    	private MenuItem _reset = new MenuItem("Reset lastID", 100, 200)
    	{
    		public void run()
    		{
    			properties = Properties.fetch();
    			properties.setLastID(0, 0);
    			properties.setLastID(1, 0);
    			properties.setLastID(2, 0);
    			properties.setLastID(3, 0);
    			properties.save();
    			Dialog.inform("Cleared last ID");
    		}
    	};
    	
    	private MenuItem _back = new MenuItem("Back", 200, 100)
    	{
    		public void run()
    		{
    			onClose();
    		}
    	};
    	
    	private MenuItem _cancelNotice = new MenuItem("Reset Notifications", 150, 150)
    	{
    		public void run()
    		{
    			try
    			{
    				NotificationsManager.cancelImmediateEvent(NOTIFICATIONS_ID_1, 99, this, this);
    			}
    			catch (Exception e)
    			{
    				// Exception can be raised if not notifying, ignore.
    			}
    		}
    	};
    	
    	protected void makeMenu(Menu menu, int instance)
    	{
    		menu.add(_reset);
    		menu.add(_cancelNotice);
    		menu.add(_back);
    		menu.addSeparator();
    		super.makeMenu(menu, instance);
    	}
    }
    
    final class UpdateScreen extends MainScreen
    {	
    	protected void onExposed()
    	{
    		// Stop notifications on focus
			try
			{
				NotificationsManager.cancelImmediateEvent(NOTIFICATIONS_ID_1, 99, "bbtweet", "GUILaunch");
			}
			catch (Exception e)
			{
				// If app is not notifying an exception may be raised. Ignore.
			}
    	}
    	
    	public boolean onClose()
    	{
    		UiApplication.getUiApplication().requestBackground();
    		return false;
    	}
    	
    	protected void makeMenu(Menu menu, int instance)
    	{
    		menu.add(_update);
    		menu.add(_options);
    		menu.add(_about);
    		super.makeMenu(menu, instance);
    	}
    	
    	private MenuItem _about = new MenuItem("About", 0x00050100, 300)
    	{
    		public void run()
    		{
    			pushScreen(new AboutScreen());
    		}
    	};
    	
    	private MenuItem _options = new MenuItem("Options", 0x00050000, 200)
    	{
    		public void run()
    		{
    			pushScreen(new OptionsScreen());
    		}
    	};
    	
    	private MenuItem _update = new MenuItem("Update", 0x00040000, 100)
    	{
    		public void run()
    		{
    			//Check that credentials have been entered
    			properties = Properties.fetch();
    			if (properties.getAuthenticationHash() == null)
    			{
    				Dialog.alert("Cannot update status until credentials are entered on Options screen");
    				return;
    			}
    			
    			//updatingDlg.show();
    			// Status.show("Updating Status", Bitmap.getBitmapResource("img/bbTweet_jde.png"), 2000);
            	properties.setStatusUpdate(_statusUpdateField.getText());
            	properties.setPushStatusUpdate(1);
            	properties.save();
             	updateDlg.show();
             	_statusMonitor = new StatusMonitor();
             	_statusUpdater = new StatusUpdater();
             	_statusMonitor.start();
             	_statusUpdater.start();
    		}
    	}; 
    	
    	
    	
    	public boolean onSavePrompt()
    	{
    		properties = Properties.fetch();
    		properties.setStatusUpdate(_statusUpdateField.getText());
    		properties.save();
    		
    		_statusUpdateField = null;
    		return true;
    	}
    }
    
    final class OptionsScreen extends MainScreen
    {
    	
    	private ObjectChoiceField enabledChoice;
        private EditField usernameEdit;
        private PasswordEditField passwordEdit;
        private NumericChoiceField friendsIntervalChoice;
        private NumericChoiceField repliesIntervalChoice;
        private NumericChoiceField directMsgIntervalChoice;
        private NumericChoiceField statusUpdateRetryAttemptsChoice;
        private Properties properties;
    	
    	public OptionsScreen()
    	{
    		// Set Screen Title
            LabelField title = new LabelField("bbtweet Options" , LabelField.ELLIPSIS | LabelField.USE_ALL_WIDTH);
            setTitle(title);
    		
    		//Read in the properties from the persistent store.
            properties = Properties.fetch();

            //Get the current values and push the screen
            usernameEdit = new EditField("Username: ", properties.getUsername(),
            		BasicEditField.DEFAULT_MAXCHARS, BasicEditField.NO_NEWLINE);
            add(usernameEdit);
            
            passwordEdit = new PasswordEditField("Password: ", "",
            		PasswordEditField.DEFAULT_MAXCHARS, 0);
            add(passwordEdit);
            
            Object[] enabledChoices = {"Disabled", "Enabled"};
            int enabledSelection = properties.isBbtweetEnabled() ? 1 : 0;
            enabledChoice = new ObjectChoiceField("Update Retrieval: ", enabledChoices, enabledSelection);
            add(enabledChoice);
            
            friendsIntervalChoice = new NumericChoiceField("Friends timeline update interval: ", 0, 120, 1);
            friendsIntervalChoice.setSelectedValue(properties.getFriendsInterval());
            add(friendsIntervalChoice);
            
            repliesIntervalChoice = new NumericChoiceField("@Replies update interval: ", 0, 120, 1);
            repliesIntervalChoice.setSelectedValue(properties.getRepliesInterval());
            add(repliesIntervalChoice);
            
            directMsgIntervalChoice = new NumericChoiceField("Direct messages update interval: ", 0, 120, 1);
            directMsgIntervalChoice.setSelectedValue(properties.getDirectInterval());
            add(directMsgIntervalChoice);
            
            statusUpdateRetryAttemptsChoice = new NumericChoiceField("Status Update Retries: ", 1, 10, 1);
            statusUpdateRetryAttemptsChoice.setSelectedValue(properties.getStatusUpdateRetryAttempts());
            add(statusUpdateRetryAttemptsChoice);
    	}
    	
    	public boolean isDataValid()
    	{
    		// Detect special case where username was updated and password needs to be re-entered to compute hash
    		// Allow hash to be cleared out if username is cleared
        	if ((usernameEdit.getText().compareTo(properties.getUsername()) != 0) && (!(passwordEdit.getTextLength() > 0))
        			&& !("".equals(usernameEdit.getText())))
        	{
        		Dialog.alert("Please enter your password");
        		return false;
        	}
        	else if (("".equals(usernameEdit.getText())) && (enabledChoice.getSelectedIndex() == 1)) 
        	{ // Must have credentials if Enabled
        		Dialog.alert("Please enter credentials to enable update retrieval");
        		return false;
        	}
        	else if ((friendsIntervalChoice.getSelectedValue() == 0 ? 0 : (60 / friendsIntervalChoice.getSelectedValue()))
        			+ (repliesIntervalChoice.getSelectedValue() == 0 ? 0 : (60 / repliesIntervalChoice.getSelectedValue()))
        			+ (directMsgIntervalChoice.getSelectedValue() == 0 ? 0 : (60 / directMsgIntervalChoice.getSelectedValue())) > 100)
        	{
        		Dialog.alert("Clients are only allowed 100 request per 60 minute interval, please adjust update intervals.");
        		return false;
        	}
        	else
        	{
        		// As long as password is entered when username is changed, all other data should be valid
        		return true;
        	}
    	}
    	
    	public void save()
        {
        	properties = Properties.fetch();
            
    		// Set enabled, setting last ID to initialization token if not
    		if (enabledChoice.getSelectedIndex() == 1)
            {
            	properties.setBbtweetEnabled(true);
            }
            else
            {
            	properties.setBbtweetEnabled(false);
            	properties.setLastID(0, -1);
            	properties.setLastID(1, -1);
            	properties.setLastID(2, -1);
            }
    		
    		// Set username & password
            properties.setUsername(usernameEdit.getText());
            if (passwordEdit.getTextLength() > 0) // Update password if changed
            	properties.setPassword(passwordEdit.getText());
            
            // Set other properties
            properties.setFriendsInterval(friendsIntervalChoice.getSelectedValue());
            properties.setRepliesInterval(repliesIntervalChoice.getSelectedValue());
            properties.setDirectInterval(directMsgIntervalChoice.getSelectedValue());
            properties.setStatusUpdateRetryAttempts(statusUpdateRetryAttemptsChoice.getSelectedValue());

            //Write changes.
            properties.save();
            
            // Reset timers
            _connectionManager = _connectionManager.killTimers();
            
            //Clean up
            enabledChoice = null;
            usernameEdit = null;
            passwordEdit = null;
            friendsIntervalChoice = null;
            properties = null;
        }
    	
    	protected void makeMenu(Menu menu, int instance)
    	{
    		menu.add(_save);
    		super.makeMenu(menu, instance);
    	}
    	
    	private MenuItem _save = new MenuItem("Save", 100, 10)
    	{
    		public void run()
    		{
    			if (onSave())
    				close();
    		}
    	};
    	
    }
    
    class StatusMonitor extends Thread
    {
    	private volatile boolean _stop = false;
    	
    	public void stop()
    	{
    		_stop = true;
    	}
    	
    	public void run()
    	{
        	while (!_stop)
        	{
        		properties = Properties.fetch();
        		int status = properties.getPushStatusUpdate();
        		if (status == 2) //Success
        		{
        			UiApplication.getUiApplication().invokeLater(new Runnable() 
        	        {
        	            public void run()
        	            {
        	                // remove update dialog and shutdown thread
        	            	_statusUpdateField.clear(0);
        	            	try
        	            	{
        	            		popScreen(updateDlg);
        	            	}
        	            	catch (IllegalArgumentException e)
        	            	{
        	            		//Screen wasn't on the stack, don't need to do anything
        	            	}
        	            	Status.show("Success!", Bitmap.getBitmapResource("img/bbTweet1_jde.png"), 1000);
        	            }
        	            
        	        });
        			
        			properties = Properties.fetch();
                    properties.setPushStatusUpdate(0);
                    properties.setStatusUpdate("");
                    properties.save();
        			
        			// break
        			stop();
        		}
        		else if (status == 3) //Failure
        		{
        			UiApplication.getUiApplication().invokeLater(new Runnable() 
        	        {
        	            public void run()
        	            {
        	                // remove update dialog and shutdown thread
        	            	try
        	            	{
        	            		popScreen(updateDlg);
        	            	}
        	            	catch (IllegalArgumentException e)
        	            	{
        	            		//Screen wasn't on the stack, don't need to do anything
        	            	}
        	            	requestForeground();
        	            	errDlg.doModal();
        	            } 
        	        });
        			
        			properties = Properties.fetch();
                    properties.setPushStatusUpdate(0);
                    properties.save();
        			stop();
        		}
    		
    		// sleep
    		try
    		{
    			sleep(1000);
    		}
    		catch (InterruptedException e)
    		{
    			// couldn't sleep
    		}
        	
        	}
        	return;
    	}
    		
    }
    

}


